
#include "itplay.h"
#include <stddef.h>


#ifdef _IT_DEBUG_CUSTOM_H
	#include _IT_DEBUG_CUSTOM_H
#else
	#ifdef _IT_DEBUG
		#include <stdio.h>

		#define _IT_DEBUG_PRINTF( m_arg )\
		do {\
			fprintf(stdout , m_arg );\
		} while (0)

			#define _IT_ERROR_PRINT( m_arg )\
			do {\
				fprintf(stdout , "** %s:%i ** IT_PLAY ERROR:  ", __FILE__, __LINE__);\
				fprintf(stdout , m_arg );\
				fprintf(stdout , "\n");\
			} while (0)

	#else
		/* Or Implement as you wish */
		#define _IT_DEBUG_PRINT( m_arg ) \
		do { } while(0)
		#define _IT_ERROR_PRINT( m_arg ) \
		do { } while(0)

	#endif

#endif

#define _IT_SW_MAX_SAMPLES 512
#define _IT_SW_VOL_FRAC_SIZE 8
#define _IT_SW_DESIRED_LATENCY_MS 10
#define _IT_SW_SAMPLE_PADDING 2

/* DO NOT CHANGE!!!!!!!! RELIES ON WRAPAROUND OF s16 */
#define _IT_SW_FRAC_SIZE 16
#define _IT_SW_FRAC_MASK ((1<<_IT_SW_FRAC_SIZE)-1)

static void _it_zero_mem(void * p_mem, it_u32 p_bytes) {

	it_u8 *mem=(it_u8*)p_mem;
	int i;
	for (i=0;i<p_bytes;i++)
		mem[i]=0;
}

typedef struct {

	IT_SampleID sample;
	it_s32 increment_fp;

	it_s32 oldvol_r,oldvol_l; /* volume ramp history */
	it_s32 offset;
	it_u16 frac;

	it_bool start; /* mixing for the first time with new parameters */
	it_bool active;
	it_u8 volume; /* 0 .. 255 */
	it_u8 pan;  /* 0 .. 255 */

} _IT_SoftMixVoice;

typedef struct {

	IT_Mixer _mixer;
	IT_SampleData sample_pool[_IT_SW_MAX_SAMPLES];
	void* sample_data_pool[_IT_SW_MAX_SAMPLES];
	_IT_SoftMixVoice *voices;

	it_s32 *block_buffer;
	it_u32 block_bits;
	it_u32 block_size;
	it_u32 block_left_to_mix;

	it_s32 * mixdown_buffer;
	it_u32 mix_rate;

	it_u32 callback_interval;
	it_u32 callback_interval_remainder;

	it_u8 max_voices;
	void (*process_callback)(void*);
	void* userdata;

	IT_MemoryManager *memory;

} _IT_SoftMix;


void _itmix_mix_voice( _IT_SoftMix *m, it_u8 p_voice ) {

	/* some pointers.. */

	_IT_SoftMixVoice *v=&m->voices[p_voice];
	IT_SampleData *s=&m->sample_pool[ v->sample ];
	void *sd=m->sample_data_pool[ v->sample ];
	it_s32 *mix_pos=m->block_buffer;
	IT_LoopType loop_type;


	it_u32 vol=v->volume; /* 32 bits version of the volume */

	/* some 64-bit fixed point precaches */
	it_u32 loop_begin=s->loop_begin;
	it_u32 loop_end=s->loop_end;
	it_u32 length=s->length;
	it_u32 begin_limit=(s->loop_type!=IT_LOOP_DISABLED)?loop_begin:0;
	it_u32 end_limit=(s->loop_type!=IT_LOOP_DISABLED)?loop_end:length;
	it_s32 todo=m->block_size;
	it_s32 vol_l,vol_r;
	it_s32 dst_vol_l,dst_vol_r;
	it_s32 vol_l_inc,vol_r_inc;
	/* check that sample is valid */

	if (!sd) {
		/* if sample dissapeared, disable voice */
		v->active=it_false;
		return;
	}

	/* compute voice left and right volume multipliers */

	dst_vol_l=(vol * (255-v->pan)) >> 8;
	dst_vol_r=(vol * (v->pan)) >> 8;

	if (v->start) { /* first time, reset ramp */
		v->oldvol_l=dst_vol_l;
		v->oldvol_r=dst_vol_r;
	}

	/* compute vlume ramps */

	vol_l=v->oldvol_l<<_IT_SW_VOL_FRAC_SIZE;
	vol_r=v->oldvol_r<<_IT_SW_VOL_FRAC_SIZE;
	vol_l_inc=((dst_vol_l-v->oldvol_l)<<_IT_SW_VOL_FRAC_SIZE) >> m->block_bits;
	vol_r_inc=((dst_vol_r-v->oldvol_r)<<_IT_SW_VOL_FRAC_SIZE) >> m->block_bits;

	loop_type = s->loop_type;

	while (todo>0) {

		it_s32 limit=0;
		it_s32 target=0,aux=0;

		/** LOOP CHECKING **/

		if ( v->increment_fp < 0 ) {
			/* going backwards */

			if(  loop_type!=IT_LOOP_DISABLED && v->offset < loop_begin ) {
				/* loopstart reached */

				if ( loop_type==IT_LOOP_PING_PONG ) {
					/* bounce ping pong */
					v->offset= loop_begin + ( loop_begin-v->offset );
					v->frac=0xFFFF-v->frac;
					v->increment_fp=-v->increment_fp;
				} else {
					/* go to loop-end */
					v->offset=loop_end-(loop_begin-v->offset);
				}
			} else {
				/* check for sample not reaching begining */
				if(v->offset < 0) {

					v->active=it_false;
					break;
				}
			}
		} else {
			/* going forward */
			if(  loop_type!=IT_LOOP_DISABLED && v->offset >= loop_end ) {
				/* loopend reached */

				if ( loop_type==IT_LOOP_PING_PONG ) {
					/* bounce ping pong */
					v->offset=loop_end-(v->offset-loop_end);
					v->frac=0xFFFF-v->frac;
					v->increment_fp=-v->increment_fp;
				} else {
					/* go to loop-begin */

					v->offset=loop_begin+(v->offset-loop_end);
				}
			} else {
				/* no loop, check for end of sample */
				if(v->offset >= length) {

					v->active=it_false;

					break;
				}
			}
		}

		/** MIXCOUNT COMPUTING **/

		/* next possible limit (looppoints or sample begin/end */
		limit=(v->increment_fp < 0)?begin_limit:end_limit;

		/* compute what is shorter, the todo or the limit? */
		aux=(limit-v->offset)/v->increment_fp+1;
		target=(aux<todo)?aux:todo; /* mix target is the shorter buffer */

		/* check just in case */
		if ( target<=0 ) {

			v->active=it_false;
			break;
		}

		todo-=target;
		/* not mixing stereo for now */
		switch ( s->format ) {

			case IT_SAMPLE_FORMAT_PCM8: { /* signed 8-bits */

				/* convert to local mixing chunk so
				    32 bits resampling can be used */
				it_s8 *src_ptr =  &((it_s8*)sd)[v->offset+_IT_SW_SAMPLE_PADDING];
				it_s32 offset=v->frac;

				while (target--) {

					it_s32 val = src_ptr[offset>>_IT_SW_FRAC_SIZE];
					it_s32 val_next = src_ptr[(offset>>_IT_SW_FRAC_SIZE) +1];
					val<<=8; /* convert to 16 */
					val_next<<=8; /* convert to 16 */

					val=val+((val_next-val)*(offset&_IT_SW_FRAC_MASK) >> _IT_SW_FRAC_SIZE);

					*(mix_pos++) += val * (vol_l>>_IT_SW_VOL_FRAC_SIZE);
					*(mix_pos++) += val * (vol_r>>_IT_SW_VOL_FRAC_SIZE);

					vol_l+=vol_l_inc;
					vol_r+=vol_r_inc;
					offset+=v->increment_fp;

				}

				v->offset+=offset>>_IT_SW_FRAC_SIZE;
				v->frac=offset&_IT_SW_FRAC_MASK;

			} break;
			case IT_SAMPLE_FORMAT_PCM16: { /* signed 16-bits */

				/* convert to local mixing chunk so
				    32 bits resampling can be used */
				it_s8 *src_ptr =  &((it_s8*)sd)[v->offset+_IT_SW_SAMPLE_PADDING];
				it_s32 offset=v->frac;

				while (target--) {

					it_s32 val = src_ptr[offset>>_IT_SW_FRAC_SIZE];
					it_s32 val_next = src_ptr[(offset>>_IT_SW_FRAC_SIZE) +1];

					val=val+((val_next-val)*(offset&_IT_SW_FRAC_MASK) >> _IT_SW_FRAC_SIZE);

					*(mix_pos++) += val * (vol_l>>_IT_SW_VOL_FRAC_SIZE);
					*(mix_pos++) += val * (vol_r>>_IT_SW_VOL_FRAC_SIZE);

					vol_l+=vol_l_inc;
					vol_r+=vol_r_inc;

					offset+=v->increment_fp;

				}

				v->offset+=offset>>_IT_SW_FRAC_SIZE;
				v->frac=offset&_IT_SW_FRAC_MASK;

			} break;

		}

	}

	v->oldvol_l=dst_vol_l;
	v->oldvol_r=dst_vol_r;
}

void _itmix_mix_block( _IT_SoftMix *m ) {

	int i;

	for(i=0;i<m->block_size*2;i++)
		m->block_buffer[i]=0;

	/* callback happens before zeroing buffer, but after mixing voices.
	   this way, declicking is possible */

	if (m->callback_interval && m->process_callback) {

		int left = m->block_size+m->callback_interval_remainder;

		while (left > m->callback_interval) {

			left-=m->callback_interval; /* do it before since interval may change in the callback */
			m->process_callback(m->userdata);

		}

		m->callback_interval_remainder=left;
	}

	for (i=0;i<m->max_voices;i++) {

		if (!m->voices[i].active) /* ignore inactive voices */
			continue;
		_itmix_mix_voice( m, i );
	}

}

it_u32 it_softmixer_mix(IT_Mixer *p_mixer,it_s32 *p_buffer,it_u32 p_frames) {

	_IT_SoftMix *m=(_IT_SoftMix*)p_mixer;

	int todo=p_frames;

	while(todo) {

		int to_mix,i;
		it_s32 *src;

		if (m->block_left_to_mix==0) {

			_itmix_mix_block(m);
			m->block_left_to_mix=m->block_size;
		}

		to_mix = (m->block_left_to_mix < todo)?m->block_left_to_mix:todo;
		src=&m->block_buffer[ m->block_size - m->block_left_to_mix ];

		for(i=0;i<to_mix*2;i++) {
			p_buffer[i]=src[i];
		}

		m->block_left_to_mix-=to_mix;
		todo-=to_mix;
		p_buffer+=to_mix*2;
	}

	return p_frames;
}

static void _itmix_set_process_callback(IT_Mixer* p_mixer, void (*p_process_callback)(void*), void* p_userdata) {

	_IT_SoftMix *m=(_IT_SoftMix*)p_mixer;
	m->process_callback=p_process_callback;
	m->userdata=p_userdata;
}

static void _itmix_set_process_callback_interval(IT_Mixer* p_mixer,it_u32 p_usec) {

	_IT_SoftMix *m=(_IT_SoftMix*)p_mixer;
	it_u32 interval=p_usec/100; /* convert to msec10 */
	interval*=m->mix_rate;
	interval/=10000L; /* convert to frames */

	m->callback_interval = interval;

}

static void _itmix_voice_stop(IT_Mixer* p_mixer,it_u8 p_voice) {

	_IT_SoftMix *m=(_IT_SoftMix*)p_mixer;

	if (p_voice>=m->max_voices) {

		_IT_ERROR_PRINT("Invalid Voice (Stop)");
		return;
	}

	if (!m->voices[p_voice].active)
		return;

	if (!m->voices[p_voice].start && m->voices[p_voice].increment_fp!=0) {
		/* this will force the volume ramper to fade the voice out, avoiding clicks */
		m->voices[p_voice].volume=0;
		_itmix_mix_voice( m, p_voice );
	}

	m->voices[p_voice].active=it_false;
}

static void _itmix_voice_start(IT_Mixer* p_mixer,it_u8 p_voice,IT_SampleID p_sample,it_u32 p_offset) {

	_IT_SoftMix *m=(_IT_SoftMix*)p_mixer;
	it_u32 base_sample_rate;

	if (p_voice>=m->max_voices) {

		_IT_ERROR_PRINT("Invalid Voice Index");
		return;
	}
	if (p_sample<0 || p_sample>=_IT_SW_MAX_SAMPLES || !m->sample_data_pool[p_sample]) {

		_IT_ERROR_PRINT("Invalid Sample");
		return;
	}

	/* STOP CURRENT VOICE IF ACTIVE (performing declick if needed) */

	if (m->voices[p_voice].active)
		_itmix_voice_stop(p_mixer,p_voice);


	/* Set and Validate new voice */

	if (p_offset>=m->sample_pool[p_sample].length) {

		m->voices[p_voice].active=it_false; /* turn off voice, offset is too long */
		return;
	}


	m->voices[p_voice].sample=p_sample;
	m->voices[p_voice].offset=p_offset;
	m->voices[p_voice].frac=0;
	m->voices[p_voice].start=it_true;
	m->voices[p_voice].active=it_true;

	if (base_sample_rate>0) {
		m->voices[p_voice].increment_fp=1;
		m->voices[p_voice].volume=255;
		m->voices[p_voice].pan=128;
	} else {
		m->voices[p_voice].increment_fp=0;
	}


}



static void _itmix_voice_set_volume(IT_Mixer* p_mixer,it_u8 p_voice,it_u8 p_vol) {

	_IT_SoftMix *m=(_IT_SoftMix*)p_mixer;

	if (p_voice>=m->max_voices) {

		_IT_ERROR_PRINT("Invalid Voice (Set Volume)");
		return;
	}

	if (!m->voices[p_voice].active)
		return;

	m->voices[p_voice].volume=p_vol;
}
static void _itmix_voice_set_pan(IT_Mixer* p_mixer,it_u8 p_voice,it_u8 p_pan) {

	_IT_SoftMix *m=(_IT_SoftMix*)p_mixer;

	if (p_voice>=m->max_voices) {

		_IT_ERROR_PRINT("Invalid Voice (Set Volume)");
		return;
	}

	if (!m->voices[p_voice].active)
		return;

	m->voices[p_voice].pan=p_pan;

}
static void _itmix_voice_set_speed(IT_Mixer* p_mixer,it_u8 p_voice,it_u32 p_hz) {

	_IT_SoftMix *m=(_IT_SoftMix*)p_mixer;

	it_bool backwards;

	if (p_voice>=m->max_voices) {

		_IT_ERROR_PRINT("Invalid Voice (Set Speed)");
		return;
	}

	if (!m->voices[p_voice].active)
		return;

	/* imcrementor in fixed point */
	backwards=(m->voices[p_voice].increment_fp<0)?it_true:it_false;

	m->voices[p_voice].increment_fp=((p_hz<<12)/m->mix_rate)<<4; /* 15:16 bits (sign) */

	if (backwards)
		m->voices[p_voice].increment_fp=-m->voices[p_voice].increment_fp;

}
static void _itmix_voice_set_filter(IT_Mixer* p_mixer,it_u8 p_voice,it_u32 p_cutoff_hz,it_u8 p_resonance) {

	_IT_SoftMix *m=(_IT_SoftMix*)p_mixer;
}

static it_bool _itmix_voice_is_active(IT_Mixer* p_mixer,it_u8 p_voice) {

	_IT_SoftMix *m=(_IT_SoftMix*)p_mixer;

	if (p_voice>=m->max_voices) {

		_IT_ERROR_PRINT("Invalid Voice (Is Active)");
		return it_false;
	}

	return m->voices[p_voice].active;
		return it_false;
}

static IT_SampleID _itmix_sample_create(IT_Mixer* p_mixer,const IT_SampleData *p_sample_data) {

	_IT_SoftMix *m=(_IT_SoftMix*)p_mixer;
	int i;
	if (p_sample_data->length==0) {

		_IT_ERROR_PRINT("Invalid Sample Length (Create)");
		return IT_INVALID_SAMPLE_ID;
	}

	for (i=0;i<_IT_SW_MAX_SAMPLES;i++) {

		int length;
		if (m->sample_data_pool[i]!=NULL)
			continue;

		length=p_sample_data->length+_IT_SW_SAMPLE_PADDING*2;
		if ( (p_sample_data->format&IT_SAMPLE_FORMAT_MASK)==IT_SAMPLE_FORMAT_PCM16 )
			length*=2;
		if ( p_sample_data->format&IT_SAMPLE_STEREO_FLAG )
			length*=2;

		m->sample_pool[i]=*p_sample_data;
		m->sample_data_pool[i]=m->memory->alloc( length, IT_MEMORY_ALLOC_SAMPLE );
		_it_zero_mem( m->sample_data_pool[i], length );

		return i;
	}

	_IT_ERROR_PRINT("Sample Pool Full (Create)");
	return IT_INVALID_SAMPLE_ID;

}

static void _itmix_sample_set_frame(IT_Mixer* p_mixer,IT_SampleID p_sample,it_u32 p_frame, it_u8 p_channel,it_s16 p_value) {

	_IT_SoftMix *m=(_IT_SoftMix*)p_mixer;
	int channels=0;

	if (p_sample<0 || p_sample >=_IT_SW_MAX_SAMPLES || m->sample_data_pool[p_sample]==NULL) {

		_IT_ERROR_PRINT("Invalid Sample (Set Frame)");
		return;
	}

	channels = ( m->sample_pool[p_sample].format&IT_SAMPLE_STEREO_FLAG ) ? 2:1;

	if (p_channel>=channels) {

		_IT_ERROR_PRINT("Invalid Sample Channel (Set Frame)");
	}

	if ( (m->sample_pool[p_sample].format&IT_SAMPLE_FORMAT_MASK)==IT_SAMPLE_FORMAT_PCM16 ) {

		it_s16 *sample=(it_s16*)m->sample_data_pool[p_sample];
		sample+=_IT_SW_SAMPLE_PADDING;
		sample[p_frame*channels+p_channel]=p_value;

	} else {

		it_s8 *sample=(it_s8*)m->sample_data_pool[p_sample];
		sample+=_IT_SW_SAMPLE_PADDING;
		sample[p_frame*channels+p_channel]=p_value>>8;

	}
}

static void _itmix_sample_free(IT_Mixer* p_mixer,IT_SampleID p_sample) {

	_IT_SoftMix *m=(_IT_SoftMix*)p_mixer;

	if (p_sample<0 || p_sample >=_IT_SW_MAX_SAMPLES || m->sample_data_pool[p_sample]==NULL) {

		_IT_ERROR_PRINT("Invalid Sample (Free)");
		return;
	}

	m->memory->free( m->sample_data_pool[p_sample], IT_MEMORY_ALLOC_SAMPLE );
	m->sample_data_pool[p_sample]=NULL;

}
static void _itmix_reset_voices(IT_Mixer* p_mixer) {

	_IT_SoftMix *m=(_IT_SoftMix*)p_mixer;
	int i;

	for (i=0;i<m->max_voices;i++) {

		_IT_SoftMixVoice *v=&m->voices[i];

		v->sample=IT_INVALID_SAMPLE_ID;
		v->increment_fp=0;

		v->oldvol_r=0;
		v->oldvol_l=0;
		v->offset=0;
		v->frac=0;

		v->start=it_false;
		v->active=it_false;
		v->volume=0;
		v->pan=0;
	}

}

static void _itmix_reset_samples(IT_Mixer* p_mixer) {

	_IT_SoftMix *m=(_IT_SoftMix*)p_mixer;
}

it_u16 _itmix_get_voice_count(struct _IT_Mixer* p_mixer) {

	_IT_SoftMix *m=(_IT_SoftMix*)p_mixer;
	return m->max_voices;
}

IT_Mixer* it_softmixer_create(int p_mix_rate,int p_max_voices,IT_MemoryManager *p_memory) {

	_IT_SoftMix *m = (_IT_SoftMix*)p_memory->alloc(sizeof(_IT_SoftMix),IT_MEMORY_ALLOC_SOFTMIXER);
	_it_zero_mem( m, sizeof(_IT_SoftMix) );

	m->_mixer.set_process_callback=_itmix_set_process_callback;
	m->_mixer.set_process_callback_interval=_itmix_set_process_callback_interval;
	m->_mixer.voice_start=_itmix_voice_start;
	m->_mixer.voice_stop=_itmix_voice_stop;
	m->_mixer.voice_set_volume=_itmix_voice_set_volume;
	m->_mixer.voice_set_pan=_itmix_voice_set_pan;
	m->_mixer.voice_set_speed=_itmix_voice_set_speed;
	m->_mixer.voice_set_filter=_itmix_voice_set_filter;
	m->_mixer.voice_is_active=_itmix_voice_is_active;
	m->_mixer.sample_create=_itmix_sample_create;
	m->_mixer.sample_set_frame=_itmix_sample_set_frame;
	m->_mixer.sample_free=_itmix_sample_free;
	m->_mixer.reset_voices=_itmix_reset_voices;
	m->_mixer.reset_samples=_itmix_reset_samples;
	m->_mixer.get_voice_count=_itmix_get_voice_count;

	m->mix_rate=p_mix_rate;
	m->voices=(_IT_SoftMixVoice*)p_memory->alloc( sizeof(_IT_SoftMixVoice)*p_max_voices, IT_MEMORY_ALLOC_SOFTMIXER );
	m->max_voices=p_max_voices;
	m->mixdown_buffer=NULL;
	m->process_callback=NULL;
	m->callback_interval=0; /* every mixing frame */
	m->callback_interval_remainder=0; /* every mixing frame */

	m->block_bits=1;

	while( (int)((float)(1<<m->block_bits) / (float)(m->mix_rate) * 1000.0) < _IT_SW_DESIRED_LATENCY_MS )
		m->block_bits++;

	m->block_size=1<<m->block_bits;
	m->block_buffer = p_memory->alloc(sizeof(it_s32)*m->block_size*2,IT_MEMORY_ALLOC_SOFTMIXER);

	m->memory=p_memory;

	return &m->_mixer;
}

void it_softmixer_free(IT_Mixer *p_mixer) {

}
